package org.jeecg.modules.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import lombok.extern.slf4j.Slf4j;

import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.dto.ActProcessDef;
import org.jeecg.modules.dto.OutProcessDefElementDto;
import org.jeecg.modules.entity.ActProcdefBusinessEntity;
import org.jeecg.modules.mapper.ActProcdefBusinessMapper;
import org.jeecg.modules.service.IActProcessDefService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.zip.ZipInputStream;

/**
 * @Description: 流程部署
 * @Author: jeecg-boot
 * @Date: 2023-12-06
 * @Version: V1.0
 */
@Slf4j
@Service
public class ActProcessDefServiceImpl implements IActProcessDefService {

    @Autowired
    public RepositoryService repositoryService;

    @Autowired
    public RuntimeService runtimeService;

    @Autowired
    public ActProcdefBusinessMapper businessMapper;

    @Override
    public IPage<ActProcessDef> listPage(ActProcessDef actProcessDef, Integer pageNo, Integer pageSize) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
                .latestVersion().orderByProcessDefinitionKey().asc();

        final String category = actProcessDef.getCategory();
        if (StringUtils.isNotBlank(category)) {
            processDefinitionQuery.processDefinitionCategory(category);
        }

        final String processKey = actProcessDef.getProcessKey();
        if (StringUtils.isNotBlank(processKey)) {
            processDefinitionQuery.processDefinitionKeyLike("%" + processKey + "%");
        }

        final String processDefName = actProcessDef.getName();
        if (StringUtils.isNotBlank(processDefName)) {
            processDefinitionQuery.processDefinitionNameLike("%" + processDefName + "%");
        }

        final Integer defVersion = actProcessDef.getVersion();
        if (defVersion != null && defVersion > 0) {
            processDefinitionQuery.processDefinitionVersion(defVersion);
        }

        final Integer isSuspended = actProcessDef.getIsSuspended();
        if (isSuspended != null && isSuspended > 0) {
            processDefinitionQuery.suspended();
        } else if (isSuspended != null && isSuspended <= 0) {
            processDefinitionQuery.active();
        }

        Page<ActProcessDef> page = new Page<ActProcessDef>(pageNo, pageSize);
        List<ProcessDefinition> processDefinitionList = processDefinitionQuery.listPage((int) page.getCurrent() - 1,
                (int) page.getSize());
        page.setTotal(processDefinitionQuery.count());
        page.setRecords(convertProcessDef(processDefinitionList));

        return page;
    }

    @Override
    public List<ActProcessDef> list(ActProcessDef actProcessDef) {
        // 得到ProcessDefinitionQuery对象,克认为即使一个查询器
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();

        // 设置条件，查询出当前所有流程定义,流程定义的key=holiday
        List<ProcessDefinition> list = processDefinitionQuery
                .orderByProcessDefinitionVersion()// 设置根据流程定义版本排序
                .desc()
                .list();

        return convertProcessDef(list);
    }

    @Override
    public List<ActProcessDef> listHistory(String procDefKey) {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        List<ProcessDefinition> list = processEngine.getRepositoryService().createProcessDefinitionQuery()
                .processDefinitionKey(procDefKey)
                .orderByProcessDefinitionVersion()
                .desc()
                .list();
        return convertProcessDef(list);
    }

    private List<ActProcessDef> convertProcessDef(List<ProcessDefinition> processDefinitionList) {

        return Optional.ofNullable(processDefinitionList).orElse(new ArrayList<>())
                .stream()
                .map(processDefinition -> (ProcessDefinitionEntity) processDefinition)
                .map(processDefinitionEntity -> {
                    final ActProcessDef processDef = new ActProcessDef();
                    BeanUtils.copyProperties(processDefinitionEntity, processDef);

                    processDef.setProcessKey(processDefinitionEntity.getKey());

                    final int version = processDefinitionEntity.getVersion();
                    processDef.setVersion(version);

                    final int suspensionState = processDefinitionEntity.getSuspensionState();
                    processDef.setIsSuspended(suspensionState == 1 ? 0 : 1);

                    return processDef;
                }).collect(Collectors.toList());
    }

    /**
     * 流程定义列表
     */
    @Override
    public Page<ProcessInstance> runningListPage(String procInsId, String procDefKey, Integer pageNo,
            Integer pageSize) {

        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();

        if (StringUtils.isNotBlank(procInsId)) {
            processInstanceQuery.processInstanceId(procInsId);
        }

        if (StringUtils.isNotBlank(procDefKey)) {
            processInstanceQuery.processDefinitionKey(procDefKey);
        }

        Page<ProcessInstance> page = new Page<ProcessInstance>(pageNo, pageSize);

        page.setTotal(processInstanceQuery.count());
        page.setRecords(processInstanceQuery.listPage((int) (page.getCurrent() - 1), (int) page.getSize()));
        return page;
    }

    @Override
    public List<OutProcessDefElementDto> listFlowElement(String procDefId) {
        BpmnModel model = repositoryService.getBpmnModel(procDefId);
        if (model != null) {
            Collection<FlowElement> flowElements = model.getMainProcess().getFlowElements();
            return Optional.ofNullable(flowElements).orElse(new ArrayList<>())
                    .stream()
                    .map(flowElement -> {
                        final OutProcessDefElementDto processDefElementDto = new OutProcessDefElementDto();
                        BeanUtils.copyProperties(flowElement, processDefElementDto);
                        processDefElementDto.setType(flowElement.getClass().getName());
                        return processDefElementDto;
                    }).collect(Collectors.toList());
        }
        return null;
    }

    /**
     * 读取资源，通过部署ID
     *
     * @param procDefId 流程定义ID
     * @param proInsId  流程实例ID
     * @param resType   资源类型(xml|image)
     */
    @Override
    public InputStream resourceRead(String procDefId, String proInsId, String resType) throws Exception {

        if (StringUtils.isBlank(procDefId)) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(proInsId)
                    .singleResult();
            procDefId = processInstance.getProcessDefinitionId();
        }
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(procDefId).singleResult();

        String resourceName = "";
        if (resType.equals("image")) {
            resourceName = processDefinition.getDiagramResourceName();
        } else if (resType.equals("xml")) {
            resourceName = processDefinition.getResourceName();
        }

        System.out.println(">>> resourceName = " + resourceName);
        System.out.println(">>> getDeploymentId = " + processDefinition.getDeploymentId());

        if (StringUtils.isBlank(resourceName)) {
            final Model model = repositoryService.createModelQuery().deploymentId(processDefinition.getDeploymentId())
                    .singleResult();
            System.out.println(">>>> model = " + model);
            byte[] sourceBytes = repositoryService.getModelEditorSource(model.getId());
            JsonNode editorNode = new ObjectMapper().readTree(sourceBytes);
            BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();

            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());

            // BpmnModel bpmnModel = bpmnJsonConverter.convertToBpmnModel(editorNode);

            ProcessDiagramGenerator processDiagramGenerator = new DefaultProcessDiagramGenerator();

            final InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, "宋体", "宋体", "宋体");

            return inputStream;
        }

        return repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
    }

    @Override
    public InputStream getFlowchart(String procDefId) throws Exception {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(procDefId);
        ProcessDiagramGenerator processDiagramGenerator = new DefaultProcessDiagramGenerator();
        return processDiagramGenerator.generateDiagram(bpmnModel, "宋体", "宋体", "宋体");
    }

    @Override
    public ActProcdefBusinessEntity findByProcdefId(@Valid String procdefId) throws Exception {
        QueryWrapper<ActProcdefBusinessEntity> qWrapper = new QueryWrapper<>();
        qWrapper.lambda().eq(ActProcdefBusinessEntity::getProcdefId, procdefId);
        return businessMapper.selectOne(qWrapper);
    }

    @Override
    public int updateProcdefBusiness(ActProcdefBusinessEntity business) throws Exception {
        log.info("updateProcdefBusiness: {}", business);
        return businessMapper.updateById(business);
    }

    /**
     * 保存流程定义业务数据
     * 
     * @param business
     * @throws Exception
     */
    @Override
    public int saveProcdefBusiness(ActProcdefBusinessEntity business) throws Exception {
        String procDefId = business.getProcdefId();

        if (StringUtils.isBlank(procDefId)) {
            throw new IllegalArgumentException("参数：procdefId 不能为空！");
        }

        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(procDefId).singleResult();
        int procdefVersion = processDefinition.getVersion();

        business.setId(IdWorker.getIdStr(business));
        business.setProcdefVersion(procdefVersion);

        log.info("保存流程定义业务数据, {}", business);
        return businessMapper.insert(business);
    }

    /**
     * 部署流程 - 保存
     *
     * @param file
     * @return
     */
    @Override
    @Transactional(readOnly = false)
    public String deploy(String category, MultipartFile file) {

        String message = "";

        String fileName = file.getOriginalFilename();

        try {
            InputStream fileInputStream = file.getInputStream();
            Deployment deployment;
            String extension = FilenameUtils.getExtension(fileName);
            if (extension.equals("zip") || extension.equals("bar")) {
                ZipInputStream zip = new ZipInputStream(fileInputStream);
                deployment = repositoryService.createDeployment().addZipInputStream(zip).deploy();
            } else if (extension.equals("png")) {
                deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).deploy();
            } else if (fileName.contains("bpmn20.xml")) {
                deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).deploy();
            } else if (extension.equals("bpmn")) { // bpmn扩展名特殊处理，转换为bpmn20.xml
                String baseName = FilenameUtils.getBaseName(fileName);
                deployment = repositoryService.createDeployment()
                        .addInputStream(baseName + ".bpmn20.xml", fileInputStream).deploy();
            } else {
                message = "不支持的文件类型：" + extension;
                return message;
            }

            List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
                    .deploymentId(deployment.getId()).list();

            // 设置流程分类
            for (ProcessDefinition processDefinition : list) {
                repositoryService.setProcessDefinitionCategory(processDefinition.getId(), category);
                message += "部署成功，流程ID=" + processDefinition.getId() + "<br/>";
            }

            if (list.isEmpty()) {
                message = "部署失败，没有流程。";
            }

        } catch (Exception e) {
            throw new ActivitiException("部署失败！", e);
        }
        return message;
    }

    /**
     * 设置流程分类
     */
    @Transactional(readOnly = false)
    public void updateCategory(String procDefId, String category) {
        repositoryService.setProcessDefinitionCategory(procDefId, category);
    }

    /**
     * 挂起、激活流程实例
     *
     * @param procDefId
     * @param state
     * @return
     */
    @Override
    @Transactional(readOnly = false)
    public String updateState(String procDefId, String state) {
        if (state.equals("active")) {
            repositoryService.activateProcessDefinitionById(procDefId, true, null);
            return "已激活ID为[" + procDefId + "]的流程定义。";
        } else if (state.equals("suspend")) {
            repositoryService.suspendProcessDefinitionById(procDefId, true, null);
            return "已挂起ID为[" + procDefId + "]的流程定义。";
        }
        return "无操作";
    }

    /**
     * 将部署的流程转换为模型
     *
     * @param procDefId
     * @throws UnsupportedEncodingException
     * @throws XMLStreamException
     */
    @Override
    @Transactional(readOnly = false)
    public Model convertToModel(String procDefId) throws IOException, XMLStreamException, TranscoderException {

        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(procDefId).singleResult();
        InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                processDefinition.getResourceName());
        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
        XMLStreamReader xtr = xif.createXMLStreamReader(in);
        BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);

        BpmnJsonConverter converter = new BpmnJsonConverter();
        ObjectNode modelNode = converter.convertToJson(bpmnModel);
        org.activiti.engine.repository.Model modelData = repositoryService.newModel();
        modelData.setKey(processDefinition.getKey());
        modelData.setName(processDefinition.getName());
        modelData.setCategory(processDefinition.getCategory());// .getDeploymentId());
        modelData.setDeploymentId(processDefinition.getDeploymentId());
        modelData.setVersion(Integer.parseInt(
                String.valueOf(repositoryService.createModelQuery().modelKey(modelData.getKey()).count() + 1)));

        ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, modelData.getVersion());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
        modelData.setMetaInfo(modelObjectNode.toString());

        repositoryService.saveModel(modelData);
        repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));

        return modelData;
    }

    @Override
    public void deleteByDeployId(String deploymentId) {
        /* 不带级联的删除：只能删除没有启动的流程，如果流程启动，就会抛出异常 */
        repositoryService.deleteDeployment(deploymentId);
        /* 级联删除：不管流程是否启动，都能可以删除（emmm大概是一锅端） */
        // repositoryService.deleteDeployment(deploymentId, true);
    }
}
